The dotdensity R package provides dot-density functions can be used with any kind of data, but it has been designed with hierarchical geographic data, like census data, in mind.

Age groups

For this example we are interested in the geographic distrbution of ages groups. We will make use of the cancensus package to obtain census data on the age group data for the City of Vancouver and Point Grey Penninsula (the area to the west of the city).

#devtools::install_github('mountainmath/dotdensity')
library(dotdensity)
#devtools::install_github('mountainmath/cancensus')
library(cancensus)
# options(cancensus.api_key)='<your API key>'

Using the CensusMapper API tool we select the region and variables we need.

dataset='CA16'
regions=list(CT=c("9330069.01","9330069.02"),CSD=c("5915022","5915803")) #list(CMA="59933")
vectors=c("v_CA16_4","v_CA16_64","v_CA16_82","v_CA16_100","v_CA16_118","v_CA16_136","v_CA16_154","v_CA16_172","v_CA16_190","v_CA16_208","v_CA16_226","v_CA16_244")
census_data <- cancensus.load(dataset='CA16', regions=regions, vectors=vectors, level='CT')
OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_7d530bb2f6912fbf45cde8f00d7e3134.geojson", layer: "OGRGeoJSON"
with 8 features
It has 14 fields

We have defined a convenience function prep_data that renames variables.

Armed with that we load in the geographic data for the dissemination blocks and the census data for the dissemination areas. As a pre-caution the census tract level data in case we are missing dissemmination block level data due to privacy or quality concerns, which in this particular case wasn’t the case.

data_ct <- cancensus.load(geo_format='sp',labels='short',dataset=dataset, regions=regions, vectors=vectors,level="CT") %>% prep_data
OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_82d598f6dd4811b948d8cc7aaac03edd.geojson", layer: "OGRGeoJSON"
with 120 features
It has 11 fields
data_da <- cancensus.load(geo_format='sp',labels='short',dataset=dataset, regions=regions, vectors=vectors,level="DA") %>% prep_data
OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_63e21d24e30bf0c7ffe020b6f6208adc.geojson", layer: "OGRGeoJSON"
with 1013 features
It has 10 fields
data_db <- cancensus.load(geo_format='sp',labels='short',dataset=dataset, regions=regions, vectors=vectors,level="DB") 
OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_64e0f25c6ce0caf01160ed4ce2d34c25.geojson", layer: "OGRGeoJSON"
with 4723 features
It has 10 fields

Mapping

The categories we want to map consist of all the loaded census language variables. We pick colours to represent these, decide on a scale (how many dots per household) to map as well as the opacity and size of each dot.

# Set the categorie we want to map. Those are the labels except we want to replace the "Total" with the "Other" column
categories=attributes(data_ct)$dot_labels$Detail
colors=c("#0000ff", "#ff0000", "#ffff00", "#00ff00", "#00ffff")
scale=10
alpha=0.75
size=0.5

All that’s left to do is re-aggregate the data and compute the dot locations and map them.

data_da@data <- dot_density.proportional_re_aggregate(data=data_da@data,parent_data=data_ct@data,geo_match=setNames("GeoUID","CT_UID"),categories=categories,base="Population")
data_db@data <- dot_density.proportional_re_aggregate(data=data_db@data,parent_data=data_da@data,geo_match=setNames("GeoUID","DA_UID"),categories=categories,base="Population")
dots.db <- dot_density.compute_dots(geo_data = data_db, categories = categories, scale=scale)
basemap + dot_density.dots_map(dots=dots.db,alpha=alpha,size=size)

Takeaway

By changing a couple of lines of code in the previous example about languages spoken at home in Vancouver and taking out explanatory steps we could easily build a dot-density map of age groups. Using cancensus, pulling in the relevant data was a breeze, and the dotdensity package did all the relevant dot-density calculations for us.

LS0tCnRpdGxlOiAiQWdlIGdyb3VwcyIKYXV0aG9yOiAiSmVucyB2b24gQmVyZ21hbm4iCmRhdGU6ICIyMDE3LTA4LTEwIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKdmlnbmV0dGU6ID4KICAlXFZpZ25ldHRlSW5kZXhFbnRyeXtWaWduZXR0ZSBUaXRsZX0KICAlXFZpZ25ldHRlRW5naW5le2tuaXRyOjpybWFya2Rvd259CiAgJVxWaWduZXR0ZUVuY29kaW5ne1VURi04fQotLS0KClRoZSBbYGRvdGRlbnNpdHlgIFIgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL21vdW50YWluTWF0aC9kb3RkZW5zaXR5KSBwcm92aWRlcyBkb3QtZGVuc2l0eSBmdW5jdGlvbnMgY2FuIGJlIHVzZWQgd2l0aCBhbnkga2luZCBvZiBkYXRhLCBidXQgaXQgaGFzIGJlZW4gZGVzaWduZWQgd2l0aCBoaWVyYXJjaGljYWwKZ2VvZ3JhcGhpYyBkYXRhLCBsaWtlIGNlbnN1cyBkYXRhLCBpbiBtaW5kLiAKCiMjIEFnZSBncm91cHMKRm9yIHRoaXMgZXhhbXBsZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgZ2VvZ3JhcGhpYyBkaXN0cmJ1dGlvbiBvZiBhZ2VzIGdyb3Vwcy4gCldlIHdpbGwgbWFrZSB1c2Ugb2YgdGhlIFtjYW5jZW5zdXNdKGh0dHBzOi8vZ2l0aHViLmNvbS9tb3VudGFpbk1hdGgvY2FuY2Vuc3VzKSBwYWNrYWdlIHRvIG9idGFpbiBjZW5zdXMKZGF0YSBvbiB0aGUgYWdlIGdyb3VwIGRhdGEgZm9yIHRoZSBDaXR5IG9mIFZhbmNvdXZlciBhbmQgUG9pbnQgR3JleSBQZW5uaW5zdWxhICh0aGUgYXJlYSB0byB0aGUgd2VzdCBvZiB0aGUgY2l0eSkuCgpgYGB7cn0KI2RldnRvb2xzOjppbnN0YWxsX2dpdGh1YignbW91bnRhaW5tYXRoL2RvdGRlbnNpdHknKQpsaWJyYXJ5KGRvdGRlbnNpdHkpCiNkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ21vdW50YWlubWF0aC9jYW5jZW5zdXMnKQpsaWJyYXJ5KGNhbmNlbnN1cykKIyBvcHRpb25zKGNhbmNlbnN1cy5hcGlfa2V5KT0nPHlvdXIgQVBJIGtleT4nCmBgYAoKCgpVc2luZyB0aGUgW0NlbnN1c01hcHBlciBBUEkgdG9vbF0oaHR0cHM6Ly9jZW5zdXNtYXBwZXIuY2EvYXBpL0NBMTYpIHdlIHNlbGVjdCB0aGUgcmVnaW9uIGFuZAp2YXJpYWJsZXMgd2UgbmVlZC4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRhdGFzZXQ9J0NBMTYnCnJlZ2lvbnM9bGlzdChDVD1jKCI5MzMwMDY5LjAxIiwiOTMzMDA2OS4wMiIpLENTRD1jKCI1OTE1MDIyIiwiNTkxNTgwMyIpKSAjbGlzdChDTUE9IjU5OTMzIikKdmVjdG9ycz1jKCJ2X0NBMTZfNCIsInZfQ0ExNl82NCIsInZfQ0ExNl84MiIsInZfQ0ExNl8xMDAiLCJ2X0NBMTZfMTE4Iiwidl9DQTE2XzEzNiIsInZfQ0ExNl8xNTQiLCJ2X0NBMTZfMTcyIiwidl9DQTE2XzE5MCIsInZfQ0ExNl8yMDgiLCJ2X0NBMTZfMjI2Iiwidl9DQTE2XzI0NCIpCmNlbnN1c19kYXRhIDwtIGNhbmNlbnN1cy5sb2FkKGRhdGFzZXQ9J0NBMTYnLCByZWdpb25zPXJlZ2lvbnMsIHZlY3RvcnM9dmVjdG9ycywgbGV2ZWw9J0NUJykKCmBgYAoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9CnRoZW1lX29wdHM8LWxpc3QoZ2dwbG90Mjo6dGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X3JlY3QoZmlsbCA9ICdsaWdodCBibHVlJywgY29sb3VyID0gTkEpLAogICAgICAgICAgICAgICAgICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfcmVjdChmaWxsPSJsaWdodCBncmV5IiwKICAgICAgICAgICAgICAgICAgICAgICBzaXplPTEsbGluZXR5cGU9InNvbGlkIixjb2xvcj0iYmxhY2siKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLmxpbmUgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZT0yMikpKQoKYmFzZV9nZW9tIDwtIGNhbmNlbnN1cy5sb2FkKGdlb19mb3JtYXQ9J3NwJyxkYXRhc2V0PWRhdGFzZXQsIHJlZ2lvbnM9cmVnaW9ucywgbGV2ZWw9IlJlZ2lvbnMiKQoKYmFzZW1hcCA8LSAgIGdncGxvdDI6OmdncGxvdChiYXNlX2dlb20pICsKICAgIGdncGxvdDI6Omdlb21fcG9seWdvbihnZ3Bsb3QyOjphZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSwgZmlsbCA9ICJ3aGl0ZSIsIHNpemU9MC4xKSArCiAgICAjZ2dwbG90Mjo6Z2VvbV9wb2x5Z29uKGdncGxvdDI6OmFlcyhsb25nLCBsYXQsIGdyb3VwID0gZ3JvdXApLCBjb2xvdXIgPSAiIzIyMjIyMiIsIGZpbGwgPSAid2hpdGUiLCBzaXplPTAuMSkgKwogICAgZ2dwbG90Mjo6Z3VpZGVzKGNvbG91ciA9IGdncGxvdDI6Omd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSArCiAgICBnZ3Bsb3QyOjpsYWJzKGNvbG9yID0gImxhYmVsIixjYXB0aW9uPSJTb3VyY2U6IFN0YXRDYW4gQ2Vuc3VzIDIwMTYgdmlhIGNhbmNlbnN1cyAmIENlbnN1c01hcHBlci5jYSIpICsKICAgIGdncGxvdDI6OmNvb3JkX21hcChwcm9qZWN0aW9uPSJsYW1iZXJ0IiwgbGF0MD00OSwgbGF0PTQ5LjQpICsKICAgIHRoZW1lX29wdHMKYGBgCgoKV2UgaGF2ZSBkZWZpbmVkIGEgY29udmVuaWVuY2UgZnVuY3Rpb24gYHByZXBfZGF0YWAgdGhhdCByZW5hbWVzIHZhcmlhYmxlcy4KYGBge3IsIG1lc3NhZ2U9VFJVRSwgd2FybmluZz1UUlVFLCBpbmNsdWRlPUZBTFNFfQojIHJlbmFtZSBjb2x1bW5zIGZvciBiZXR0ZXIgcmVhZGFiaWxpdHkgYW5kIGNvbXB1dGUgYWdncmVnYXRlcwpwcmVwX2RhdGEgPC0gZnVuY3Rpb24oZ2VvKXsKICBkYXRhIDwtIGdlb0BkYXRhICU+JSByZXBsYWNlKGlzLm5hKC4pLCAwKSAlPiUKICAgIG11dGF0ZSgKICAgICAgISEiMC0xOSIgOj0gdl9DQTE2XzQgKyB2X0NBMTZfNjQsCiAgICAgICEhIjIwLTM0IiA6PSB2X0NBMTZfODIgKyB2X0NBMTZfMTAwICsgdl9DQTE2XzExOCwKICAgICAgISEiMzUtNDkiIDo9IHZfQ0ExNl8xMzYgKyB2X0NBMTZfMTU0ICsgdl9DQTE2XzE3MiwKICAgICAgISEiNTAtNjQiIDo9IHZfQ0ExNl8xOTAgKyB2X0NBMTZfMjA4ICsgdl9DQTE2XzIyNiwKICAgICAgISEiNjUrIiA6PSB2X0NBMTZfMjQ0CiAgICAgICAgICAgKQogIAogIGxzPWMoIjAtMTkiLCIyMC0zNCIsIjM1LTQ5IiwiNTAtNjQiLCI2NSsiKQogIGxhYmVscyA8LSB0aWJibGUoVmVjdG9yPWxzLERldGFpbD1scykKICAKCiAgZ2VvQGRhdGEgPC0gZGF0YQogIGF0dHJpYnV0ZXMoZ2VvKSRkb3RfbGFiZWxzIDwtIGxhYmVscwogIHJldHVybihnZW8pCn0KYGBgCkFybWVkIHdpdGggdGhhdCB3ZSBsb2FkIGluIHRoZSBnZW9ncmFwaGljIGRhdGEgZm9yIHRoZSBkaXNzZW1pbmF0aW9uIGJsb2NrcyBhbmQgdGhlIGNlbnN1cyBkYXRhIGZvciB0aGUgZGlzc2VtaW5hdGlvbiBhcmVhcy4gQXMgYSBwcmUtY2F1dGlvbiB0aGUgY2Vuc3VzIHRyYWN0IGxldmVsIGRhdGEgaW4gY2FzZSB3ZSBhcmUgbWlzc2luZyBkaXNzZW1taW5hdGlvbiBibG9jayBsZXZlbCBkYXRhIGR1ZSB0byBwcml2YWN5IG9yIHF1YWxpdHkgY29uY2VybnMsIHdoaWNoIGluIHRoaXMgcGFydGljdWxhciBjYXNlIHdhc24ndCB0aGUgY2FzZS4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpkYXRhX2N0IDwtIGNhbmNlbnN1cy5sb2FkKGdlb19mb3JtYXQ9J3NwJyxsYWJlbHM9J3Nob3J0JyxkYXRhc2V0PWRhdGFzZXQsIHJlZ2lvbnM9cmVnaW9ucywgdmVjdG9ycz12ZWN0b3JzLGxldmVsPSJDVCIpICU+JSBwcmVwX2RhdGEKZGF0YV9kYSA8LSBjYW5jZW5zdXMubG9hZChnZW9fZm9ybWF0PSdzcCcsbGFiZWxzPSdzaG9ydCcsZGF0YXNldD1kYXRhc2V0LCByZWdpb25zPXJlZ2lvbnMsIHZlY3RvcnM9dmVjdG9ycyxsZXZlbD0iREEiKSAlPiUgcHJlcF9kYXRhCmRhdGFfZGIgPC0gY2FuY2Vuc3VzLmxvYWQoZ2VvX2Zvcm1hdD0nc3AnLGxhYmVscz0nc2hvcnQnLGRhdGFzZXQ9ZGF0YXNldCwgcmVnaW9ucz1yZWdpb25zLCB2ZWN0b3JzPXZlY3RvcnMsbGV2ZWw9IkRCIikgCmBgYAojIyBNYXBwaW5nCgpUaGUgY2F0ZWdvcmllcyB3ZSB3YW50IHRvIG1hcCBjb25zaXN0IG9mIGFsbCB0aGUgbG9hZGVkIGNlbnN1cyBsYW5ndWFnZSB2YXJpYWJsZXMuIFdlIHBpY2sgY29sb3VycyB0byByZXByZXNlbnQgdGhlc2UsIGRlY2lkZSBvbiBhIHNjYWxlIChob3cgbWFueSBkb3RzIHBlciBob3VzZWhvbGQpIHRvIG1hcCBhcyB3ZWxsIGFzIHRoZSBvcGFjaXR5IGFuZCBzaXplIG9mIGVhY2ggZG90LgpgYGB7cn0KIyBTZXQgdGhlIGNhdGVnb3JpZSB3ZSB3YW50IHRvIG1hcC4gVGhvc2UgYXJlIHRoZSBsYWJlbHMgZXhjZXB0IHdlIHdhbnQgdG8gcmVwbGFjZSB0aGUgIlRvdGFsIiB3aXRoIHRoZSAiT3RoZXIiIGNvbHVtbgpjYXRlZ29yaWVzPWF0dHJpYnV0ZXMoZGF0YV9jdCkkZG90X2xhYmVscyREZXRhaWwKY29sb3JzPWMoIiMwMDAwZmYiLCAiI2ZmMDAwMCIsICIjZmZmZjAwIiwgIiMwMGZmMDAiLCAiIzAwZmZmZiIpCnNjYWxlPTEwCmFscGhhPTAuNzUKc2l6ZT0wLjUKCmBgYApgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBzZXQgbWFwIHRpdGxlIHVzaW5nIHRoZSBzY2FsZSBhbmQgY29sb3VyIHZhbHVlcwp0aXRsZT1wYXN0ZTAoIlBlb3BsZSBwZXIgQWdlIEdyb3VwXG4xIGRvdCA9ICIsc2NhbGUsIiBwZW9wbGUiKQpiYXNlbWFwIDwtIGJhc2VtYXAgKyBnZ3Bsb3QyOjpzY2FsZV9jb2xvdXJfbWFudWFsKHRpdGxlLHZhbHVlcyA9IGNvbG9ycykgCmBgYAoKCkFsbCB0aGF0J3MgbGVmdCB0byBkbyBpcyByZS1hZ2dyZWdhdGUgdGhlIGRhdGEgYW5kIGNvbXB1dGUgdGhlIGRvdCBsb2NhdGlvbnMgYW5kIG1hcCB0aGVtLgpgYGB7ciwgZmlnLndpZHRoPTEyfQpkYXRhX2RhQGRhdGEgPC0gZG90X2RlbnNpdHkucHJvcG9ydGlvbmFsX3JlX2FnZ3JlZ2F0ZShkYXRhPWRhdGFfZGFAZGF0YSxwYXJlbnRfZGF0YT1kYXRhX2N0QGRhdGEsZ2VvX21hdGNoPXNldE5hbWVzKCJHZW9VSUQiLCJDVF9VSUQiKSxjYXRlZ29yaWVzPWNhdGVnb3JpZXMsYmFzZT0iUG9wdWxhdGlvbiIpCmRhdGFfZGJAZGF0YSA8LSBkb3RfZGVuc2l0eS5wcm9wb3J0aW9uYWxfcmVfYWdncmVnYXRlKGRhdGE9ZGF0YV9kYkBkYXRhLHBhcmVudF9kYXRhPWRhdGFfZGFAZGF0YSxnZW9fbWF0Y2g9c2V0TmFtZXMoIkdlb1VJRCIsIkRBX1VJRCIpLGNhdGVnb3JpZXM9Y2F0ZWdvcmllcyxiYXNlPSJQb3B1bGF0aW9uIikKCmRvdHMuZGIgPC0gZG90X2RlbnNpdHkuY29tcHV0ZV9kb3RzKGdlb19kYXRhID0gZGF0YV9kYiwgY2F0ZWdvcmllcyA9IGNhdGVnb3JpZXMsIHNjYWxlPXNjYWxlKQpiYXNlbWFwICsgZG90X2RlbnNpdHkuZG90c19tYXAoZG90cz1kb3RzLmRiLGFscGhhPWFscGhhLHNpemU9c2l6ZSkKYGBgCiMjVGFrZWF3YXkKQnkgY2hhbmdpbmcgYSBjb3VwbGUgb2YgbGluZXMgb2YgY29kZSBpbiB0aGUgW3ByZXZpb3VzIGV4YW1wbGUgYWJvdXQgbGFuZ3VhZ2VzIHNwb2tlbiBhdCBob21lIGluIFZhbmNvdXZlcl0oaHR0cHM6Ly9naXRodWIuY29tL21vdW50YWluTWF0aC9kb3RkZW5zaXR5L2Jsb2IvbWFzdGVyL3ZpZ25ldHRlcy9sYW5ndWFnZXMtZXhhbXBsZS5SbWQpIGFuZCB0YWtpbmcgb3V0IGV4cGxhbmF0b3J5IHN0ZXBzIHdlIGNvdWxkIGVhc2lseSBidWlsZCBhIGRvdC1kZW5zaXR5IG1hcCBvZiBhZ2UgZ3JvdXBzLiBVc2luZyBgY2FuY2Vuc3VzYCwgcHVsbGluZyBpbiB0aGUgcmVsZXZhbnQgZGF0YSB3YXMgYSBicmVlemUsIGFuZCB0aGUgYGRvdGRlbnNpdHlgIHBhY2thZ2UgZGlkIGFsbCB0aGUgcmVsZXZhbnQgZG90LWRlbnNpdHkgY2FsY3VsYXRpb25zIGZvciB1cy4K